home *** CD-ROM | disk | FTP | other *** search
/ Hardcore Visual Basic 5.0 (2nd Edition) / Hardcore Visual Basic 5.0 - Second Edition (1997)(Microsoft Press).iso / Code / FoldTool.cls < prev    next >
Text File  |  1997-06-14  |  18KB  |  543 lines

  1. VERSION 1.0 CLASS
  2. BEGIN
  3.   MultiUse = -1  'True
  4. END
  5. Attribute VB_Name = "GFoldTool"
  6. Attribute VB_GlobalNameSpace = True
  7. Attribute VB_Creatable = True
  8. Attribute VB_PredeclaredId = False
  9. Attribute VB_Exposed = True
  10. Option Explicit
  11.  
  12. Public Enum EErrorFoldTool
  13.     eeBaseFoldTool = 13500      ' FoldTool
  14. End Enum
  15.  
  16. Public Enum EWalkMode
  17.     ewmFolders = SHCONTF_FOLDERS
  18.     ewmNonfolders = SHCONTF_NONFOLDERS
  19.     ewmBoth = SHCONTF_FOLDERS Or SHCONTF_NONFOLDERS
  20.     ewmIncludeHidden = SHCONTF_INCLUDEHIDDEN
  21. End Enum
  22.  
  23. Private iidShellFolder As IID
  24. Private iidContextMenu As IID
  25. Private fInitialized As Boolean
  26.  
  27. Sub Class_Initialize()
  28.     ' Initialize GUIDs
  29.     iidShellFolder.Data1 = &H214E6 ' Rest of GUID is zeros
  30.     iidShellFolder.Data4(0) = &HC0
  31.     iidShellFolder.Data4(7) = &H46
  32.     iidContextMenu.Data1 = &H214E4 ' Rest of GUID is zeros
  33.     iidContextMenu.Data4(0) = &HC0
  34.     iidContextMenu.Data4(7) = &H46
  35.     ' Set initialize flag
  36.     fInitialized = True
  37. End Sub
  38.  
  39. Private Sub InitIf()
  40. #If fComponent = 0 Then
  41.     If Not fInitialized Then Class_Initialize
  42. #End If
  43. End Sub
  44.  
  45. '' Item ID helpers
  46.  
  47. ' Get the byte size of an ID list
  48. Function ItemIDSize(ByVal pidl As Long) As Integer
  49.     CopyMemory ItemIDSize, ByVal pidl, 2
  50. End Function
  51.  
  52. ' Counts the item IDs in an ID list
  53. Function PidlCount(ByVal pidl As Long) As Long
  54.     Dim cItem As Long
  55.     Do While ItemIDSize(pidl)
  56.         pidl = NextItemID(pidl)
  57.         cItem = cItem + 1
  58.     Loop
  59.     PidlCount = cItem
  60. End Function
  61.  
  62. ' Get the next item ID in an item ID list
  63. Function NextItemID(ByVal pidl As Long) As Long
  64.     Dim c As Integer
  65.     c = ItemIDSize(pidl)
  66.     If c = 0 Then Exit Function
  67.     NextItemID = pidl + c
  68. End Function
  69.  
  70. ' Duplicate an item ID (creator must free with allocator)
  71. Function DuplicateItemID(pidl As Long) As Long
  72.     Dim c As Integer, pidlNew As Long, iZero As Integer
  73.     ' Get the size
  74.     c = ItemIDSize(pidl)
  75.     If c = 0 Then Exit Function
  76.     ' Allocate space plus two for zero terminator
  77.     On Error Resume Next
  78.     pidlNew = Allocator.alloc(c + 2)
  79.     If pidlNew = pNull Then Exit Function
  80.     
  81.     ' Copy the pidl data
  82.     CopyMemory ByVal pidlNew, ByVal pidl, c
  83.     ' Terminating zero
  84.     CopyMemory ByVal pidlNew + c, iZero, 2
  85.     DuplicateItemID = pidlNew
  86. End Function
  87.  
  88. ' Concatenate two item IDs
  89. Function DuplicateItemIDs(ByVal pidl1 As Long, _
  90.                           ByVal pidl2 As Long) As Long
  91.     Dim pidlNew As Long, cb1 As Long, cb2 As Long
  92.  
  93.     ' May be NULL
  94.     If pidl1 Then
  95.         cb1 = ItemIDSize(pidl1)
  96.         If cb1 Then cb1 = cb1 - 2
  97.     End If
  98.  
  99.     cb2 = ItemIDSize(pidl2)
  100.  
  101.     pidlNew = Allocator.alloc(cb1 + cb2)
  102.     If pidlNew Then
  103.         If pidl1 Then CopyMemory ByVal pidlNew, ByVal pidl1, cb1
  104.         If pidl2 Then CopyMemory ByVal pidlNew + cb1, ByVal pidl2, cb2
  105.     End If
  106.     DuplicateItemIDs = pidlNew
  107. End Function
  108.  
  109. Function GetDesktopFolder() As IVBShellFolder
  110.     SHGetDesktopFolder GetDesktopFolder
  111. End Function
  112.  
  113. Function PathFromPidl(ByVal pidl As Long) As String
  114.     Dim s As String, f As Long
  115.     s = String$(cMaxPath, 0)
  116.     f = SHGetPathFromIDList(pidl, s)
  117.     If f Then PathFromPidl = MUtility.StrZToStr(s)
  118. End Function
  119.  
  120. Function NameFromPidl(ByVal pidl As Long) As String
  121.     Dim shfi As SHFILEINFO, f As Long
  122.     f = SHGetItemInfo(pidl, 0, shfi, LenB(shfi), _
  123.                       SHGFI_DISPLAYNAME Or SHGFI_PIDL)
  124.     If f Then NameFromPidl = MBytes.ByteZToStr(shfi.szDisplayName)
  125. End Function
  126.  
  127. Function PidlFromPath(sPath As String) As Long
  128.     Dim pidl As Long, f As Long
  129.     f = SHGetPathFromIDList(pidl, sPath)
  130.     If f Then PidlFromPath = pidl
  131. End Function
  132.  
  133. Function PathToPidl(sPath As String) As Long
  134.     InitIf
  135.     
  136.     Dim folder As IVBShellFolder, folderNext As IVBShellFolder
  137.     Dim pidlMain As Long, pidlItem As Long, pidlItemT As Long
  138.     Dim cParsed As Long, afItem As Long
  139.     Dim cItem As Long, hWnd As Long
  140.  
  141.     ' Make sure the file name is fully qualified
  142.     sPath = MUtility.GetFullPath(sPath)
  143.  
  144.     Set folder = GetDesktopFolder
  145.  
  146.     ' Convert the path name into a pointer to an item ID list (pidl)
  147.     folder.ParseDisplayName hWnd, 0, sPath, cParsed, pidlMain, afItem
  148.     
  149.     PathToPidl = pidlMain
  150.     
  151. End Function
  152.  
  153. Function ToPidl(ByVal i As Long) As Long
  154.     If i >= CSIDL_DESKTOP And (i <= CSIDL_PRINTHOOD + 4) Then
  155.         ToPidl = PidlFromSpecialFolder(i)
  156.     Else
  157.         ToPidl = i
  158.     End If
  159. End Function
  160.  
  161. Function PidlFromSpecialFolder( _
  162.                 Optional ByVal csidl As ECSIDL = CSIDL_DESKTOP, _
  163.                 Optional ByVal hWnd As Long = hNull) As Long
  164.     InitIf  ' Initialize if in standard modue
  165.     On Error Resume Next
  166.     Dim pidl As Long, e As Long
  167.     
  168.     SHGetSpecialFolderLocation hWnd, csidl, pidl
  169.     e = Err
  170.     If e = 0 Then PidlFromSpecialFolder = pidl
  171. End Function
  172.  
  173. ' Get folder and pidl from an item (path, pidl, or special folder)
  174. ' Note that caller owns returned pidl and should free it
  175. Function FolderFromItem(vItem As Variant, _
  176.                         Optional pidl As Long) As IVBShellFolder
  177.     InitIf  ' Initialize if in standard modue
  178.     
  179.     Dim folder As IVBShellFolder, folderNext As IVBShellFolder
  180.     Dim pidlItem As Long, pidlItemT As Long, cItem As Long
  181.    
  182.     pidl = pNull    ' Set reference in case of fail
  183.     On Error GoTo FolderFromItemFail
  184.     Set folder = GetDesktopFolder
  185.     If VarType(vItem) = vbString Then
  186.         ' Make sure the file name is fully qualified
  187.         vItem = MUtility.GetFullPath(CStr(vItem))
  188.     
  189.         ' Convert path name to pointer to an item ID list (pidl)
  190.         Dim cParsed As Long, afItem As Long
  191.         folder.ParseDisplayName hNull, 0, CStr(vItem), _
  192.                                 cParsed, pidlItem, afItem
  193.     Else
  194.         ' If necessary, convert special folder to pidl
  195.         pidlItem = ToPidl(vItem)
  196.     End If
  197.  
  198.     ' Walk the list of item IDs and bind to each subfolder in list
  199.     ' to find the folder containing the specified pidl
  200.     cItem = PidlCount(pidlItem)
  201.     Do While cItem
  202.         cItem = cItem - 1
  203.  
  204.         ' Create a one-item ID list for the next item in pidlMain
  205.         pidlItemT = DuplicateItemID(pidlItem)
  206.         If pidlItemT = 0 Then Exit Function
  207.  
  208.         Debug.Print GetFolderName(folder, pidlItemT, SHGDN_NORMAL)
  209.         
  210.         ' Bind to the folder specified in the new item ID list
  211.         folder.BindToObject pidlItemT, 0, _
  212.                             iidShellFolder, folderNext
  213.         
  214.         ' Release parent folder and reference current child
  215.         Set folder = folderNext
  216.         ' Free temporary pidl
  217.         Allocator.Free pidlItemT
  218.         ' Point to next item (if any)
  219.         If cItem Then pidlItem = NextItemID(pidlItem)
  220.     Loop
  221.     
  222. FolderFromItemFail:
  223.     Set FolderFromItem = folder
  224.     pidl = pidlItem
  225.     
  226.             
  227. End Function
  228.  
  229. ' Use structure from hell to get a folder name from one of three formats
  230. Function GetFolderName(folder As IVBShellFolder, ByVal pidl As Long, _
  231.                        ByVal gdn As ESHGDN) As String
  232.     InitIf  ' Initialize if in standard modue
  233.  
  234.     Dim s As String, p As Long, c As Long
  235.     Dim ab() As Byte, typefromhell As STRRET
  236.     On Error Resume Next
  237.     folder.GetDisplayNameOf pidl, gdn, typefromhell
  238.     If Err Then Err.Raise Err, "VBCore.FoldTool", ApiError(Err)
  239.     Select Case typefromhell.uType
  240.     Case STRRET_WSTR
  241.         ' Pointer to Unicode string (in first four bytes of byte array)
  242.         CopyMemory p, ByVal VarPtr(typefromhell.CStr(0)), 4
  243.         ' The only way to get string length on Win95
  244.         c = WideCharToMultiBytePtrs(CP_OEMCP, 0, p, -1, _
  245.                                     pNull, 0, vbNullString, pNull) - 1
  246.         s = String$(c, 0)
  247.         CopyMemory ByVal StrPtr(s), ByVal p, c * 2
  248.         
  249.     Case STRRET_OFFSET
  250.         ' Offset (in four bytes of byte array) from pidl to ANSI string
  251.         CopyMemory p, ByVal VarPtr(typefromhell.CStr(0)), 4
  252.         p = pidl + p
  253.         ' Gets ANSI length under either Win95 or WinNT
  254.         c = MultiByteToWideCharPtrs(CP_OEMCP, 0, p, -1, pNull, 0) - 1
  255.         ReDim ab(c - 1) As Byte
  256.         CopyMemory ab(0), ByVal p, c
  257.         s = StrConv(ab, vbUnicode)
  258.           
  259.     Case STRRET_CSTR
  260.         ' ANSI string buffer (as array of bytes)
  261.         p = VarPtr(typefromhell.CStr(0))
  262.         ' Gets ANSI length under either Win95 or WinNT
  263.         c = MultiByteToWideCharPtrs(CP_OEMCP, 0, p, -1, pNull, 0) - 1
  264.         ReDim ab(c - 1) As Byte
  265.         CopyMemory ab(0), ByVal p, c
  266.         s = StrConv(ab, vbUnicode)
  267.         
  268.     End Select
  269.     GetFolderName = s
  270. End Function
  271.  
  272. Function BindToShell(folder As IVBShellFolder, _
  273.                      ByVal pidl As Long) As IVBShellFolder
  274.     Dim folderNew As IVBShellFolder
  275.     folder.BindToObject pidl, 0, iidShellFolder, folderNew
  276.     Set BindToShell = folderNew
  277. End Function
  278.  
  279. Function FileInfoFromFolder(folder As IVBShellFolder, _
  280.                             ByVal pidl As Long) As CFileInfo
  281.     Dim gao As ESFGAO, sName As String
  282.     Static fi As New CFileInfo
  283.     gao = SFGAO_FILESYSTEM
  284.     ' Determine what type of object you have
  285.     folder.GetAttributesOf 1, pidl, gao
  286.     If gao And SFGAO_FILESYSTEM Then
  287.         ' Use folder parsing name to get file data
  288.         Dim fd As WIN32_FIND_DATA, h As Long
  289.         ' GetFolderName does horrible stuff with STRRET
  290.         sName = GetFolderName(folder, pidl, SHGDN_FORPARSING)
  291.         ' Handle drives
  292.         If Len(sName) = 3 Then
  293.             If Mid$(sName, 2, 2) = ":\" Then
  294.                 Dim drive As CDrive
  295.                 Set drive = New CDrive
  296.                 drive = sName
  297.                 With drive
  298.                     fi.CreateFromDrive .Root, .KindStr, _
  299.                                        CCur(.FreeBytes), CCur(.TotalBytes)
  300.                 End With
  301.                 Set FileInfoFromFolder = fi
  302.                 Exit Function
  303.             End If
  304.         End If
  305.         ' Handle files
  306.         h = FindFirstFile(sName, fd)
  307.         If h <> hInvalid Then
  308.             FindClose h
  309.             fi.CreateFromFile sName, fd.dwFileAttributes, _
  310.                               fd.nFileSizeLow, fd.ftLastWriteTime, _
  311.                               fd.ftLastAccessTime, fd.ftCreationTime
  312.             Set FileInfoFromFolder = fi
  313.             Exit Function
  314.         Else
  315.             BugMessage Err.LastDllError & " : " & LastApiError
  316.         End If
  317.     End If
  318.     
  319.     ' Some folders don't work with SHGetFileInfo, but GetFolderName works
  320.     sName = GetFolderName(folder, pidl, SHGDN_NORMAL)
  321.     fi.CreateFromNamePidl sName, pidl
  322.     Set FileInfoFromFolder = fi
  323. End Function
  324.  
  325. Function WalkAllFolders(folder As IVBShellFolder, foldit As IUseFolder, _
  326.                         Optional ByVal Level As Long = 0, _
  327.                         Optional ByVal ewm As EWalkMode = ewmBoth, _
  328.                         Optional ByVal hWnd As Long = hNull) As Long
  329.     InitIf  ' Initialize if in standard module
  330.     
  331.     Dim idenum As IVBEnumIDList, folderNew As IVBShellFolder
  332.     Dim pidl As Long, cFetched As Long, afAttrib As Long
  333.     
  334.     ' Get the IEnumIDList object for the given folder
  335.     On Error GoTo WalkAllFoldersFail
  336.     folder.EnumObjects hWnd, ewm, idenum
  337.     
  338.     ' Enumerate through the list of folder and nonfolder objects
  339.     On Error GoTo WalkAllFoldersFail2
  340.     Dim hRes As Long
  341.     Do
  342.         hRes = idenum.Next(1, pidl, cFetched)
  343.         ' 0 means got another, 1 means no more, anything else is error
  344.         ' but there had better not be any errors because we'll ignore them
  345.         If hRes Then Exit Do
  346.         
  347.         ' Pass to user-implemented interface to do something with folder
  348.         ' (True in return means user requested termination)
  349.         WalkAllFolders = foldit.UseFolder(Level, folder, pidl)
  350.         If WalkAllFolders Then
  351.             Allocator.Free pidl
  352.             Exit Function
  353.         End If
  354.         
  355.         ' It's not in the docs, but you pass in the attributes you want
  356.         ' to check and GetAttributes passes back whether those attributes
  357.         ' are set, ignoring all others
  358.         afAttrib = SFGAO_HASSUBFOLDER Or SFGAO_FOLDER
  359.         folder.GetAttributesOf 1, pidl, afAttrib
  360.         
  361.         ' If there are subfolders, process them recursively
  362.         If afAttrib And (SFGAO_HASSUBFOLDER Or SFGAO_FOLDER) Then
  363.             folder.BindToObject pidl, 0, iidShellFolder, folderNew
  364.             WalkAllFolders = WalkAllFolders(folderNew, foldit, Level + 1, ewm)
  365.         End If
  366. WalkAllFoldersFail2:
  367.         ' Free the pidl from Next
  368.         Allocator.Free pidl
  369.     Loop
  370. WalkAllFoldersFail:
  371.  
  372. End Function
  373.  
  374. Function WalkFolders(folder As IVBShellFolder, foldit As IUseFolder, _
  375.                      Optional UserData As Variant, _
  376.                      Optional ByVal ewm As EWalkMode = ewmBoth, _
  377.                      Optional ByVal hWnd As Long = hNull) As Long
  378.     InitIf  ' Initialize if in standard modue
  379.     
  380.     Dim idenum As IVBEnumIDList, folderNew As IVBShellFolder
  381.     Dim pidl As Long, cFetched As Long, afAttrib As Long
  382.     
  383.     ' Get the IEnumIDList object for the given folder
  384.     On Error GoTo WalkFoldersFail
  385.     folder.EnumObjects hWnd, ewm, idenum
  386.     
  387.     ' Enumerate through the list of folder and nonfolder objects
  388.     On Error GoTo WalkFoldersFail2
  389.     Dim hRes As Long
  390.     Do
  391.         hRes = idenum.Next(1, pidl, cFetched)
  392.         ' 0 means got another, 1 means no more, anything else is error
  393.         ' but there had better not be any errors because we'll ignore them
  394.         If hRes Then Exit Do
  395.         
  396.         ' Pass to user-implemented interface to do something with folder
  397.         ' (True in return means user requested termination)
  398.         WalkFolders = foldit.UseFolder(UserData, folder, pidl)
  399.         If WalkFolders Then
  400.             Allocator.Free pidl
  401.             Exit Function
  402.         End If
  403.         
  404. WalkFoldersFail2:
  405.         ' Free the pidl from Next
  406.         Allocator.Free pidl
  407.     Loop
  408. WalkFoldersFail:
  409.  
  410. End Function
  411.  
  412. ' Display a context menu from a folder
  413. ' Based on C code by Jeff Procise in PC Magazine
  414. ' Destroys any pidl passed to it, so pass duplicate if necessary
  415. Function ContextPopMenu(ByVal hWnd As Long, vItem As Variant, _
  416.                         ByVal x As Long, ByVal y As Long) As Boolean
  417.     InitIf  ' Initialize if in standard modue
  418.  
  419.     Dim folder As IVBShellFolder, pidlMenu As Long
  420.     Dim menu As IVBContextMenu, ici As CMINVOKECOMMANDINFO
  421.     Dim iCmd As Long, f As Boolean, hMenu As Long
  422.     
  423.     ' Get folder and pidl from path, pidl, or special item
  424.     Set folder = FolderFromItem(vItem, pidlMenu)
  425.     If folder Is Nothing Then Exit Function
  426.  
  427.     ' Get an IContextMenu object
  428.     On Error GoTo ContextPopMenuFail
  429.     folder.GetUIObjectOf hWnd, 1, pidlMenu, iidContextMenu, 0, menu
  430.  
  431.     ' Create an empty popup menu and initialize it with QueryContextMenu
  432.     hMenu = CreatePopupMenu
  433.     On Error GoTo ContextPopMenuFail2
  434.     menu.QueryContextMenu hMenu, 0, 1, &H7FFF, CMF_EXPLORE
  435.         
  436.     ' Convert x and y to client coordinates
  437.     MWinTool.ClientToScreenXY hWnd, x, y
  438.     
  439.     ' Display the context menu
  440.     Const afMenu = TPM_LEFTALIGN Or TPM_LEFTBUTTON Or _
  441.                    TPM_RIGHTBUTTON Or TPM_RETURNCMD
  442.     iCmd = TrackPopupMenu(hMenu, afMenu, x, y, 0, hWnd, ByVal hNull)
  443.  
  444.     ' If a command was selected from the menu, execute it.
  445.     If iCmd Then
  446.         ici.cbSize = LenB(ici)
  447.         ici.fMask = 0
  448.         ici.hWnd = hWnd
  449.         ici.lpVerb = iCmd - 1
  450.         ici.lpParameters = pNull
  451.         ici.lpDirectory = pNull
  452.         ici.nShow = SW_SHOWNORMAL
  453.         ici.dwHotKey = 0
  454.         ici.hIcon = hNull
  455.         menu.InvokeCommand ici
  456.         ContextPopMenu = True
  457.     End If
  458.     
  459. ContextPopMenuFail2:
  460.     DestroyMenu hMenu
  461.            
  462. ContextPopMenuFail:
  463.     ' Menu pidl is freed, so client had better not pass only copy
  464.     Allocator.Free pidlMenu
  465.  
  466. End Function
  467.  
  468. ' Recent document list
  469.  
  470. Sub AddToRecentDocs(sDoc As String)
  471.     SHAddToRecentDocs SHARD_PATH, sDoc
  472. End Sub
  473.  
  474. Sub ClearRecentDocs()
  475.     SHAddToRecentDocs SHARD_PATH, sNullStr
  476. End Sub
  477.  
  478. Function BrowseForFolder(Optional Owner As Long = hNull, _
  479.                          Optional DisplayName As String, _
  480.                          Optional Options As Long, _
  481.                          Optional Title As String, _
  482.                          Optional Root As Variant) As String
  483.     InitIf  ' Initialize if in standard modue
  484.     
  485.     Dim bi As BROWSEINFO
  486.     bi.hwndOwner = Owner
  487.     bi.pszDisplayName = StrPtr(String$(cMaxPath, 0))
  488.     If Title <> sEmpty Then bi.lpszTitle = StrPtr(Title)
  489.     bi.ulFlags = Options
  490.     ' bi.lpfn = 0
  491.     ' bi.lParam = 0
  492.     ' bi.iImage = 0
  493.     Dim pidlIn As Long, pidlOut As Long, sPath As String
  494.     
  495.     If IsMissing(Root) Then
  496.         pidlIn = PidlFromSpecialFolder(CSIDL_DRIVES, Owner)
  497.     ElseIf VarType(Root) = vbString Then
  498.         ' Start specified as string path
  499.         sPath = MUtility.NormalizePath(CStr(Root))
  500.         'pidlIn = PidlFromPath(sPath)
  501.         pidlIn = PathToPidl(sPath)
  502.     Else
  503.         ' Start specified as pidl
  504.         pidlIn = ToPidl(Root)
  505.     End If
  506.     
  507.     bi.pidlRoot = pidlIn
  508.     pidlOut = SHBrowseForFolder(bi)
  509.     DisplayName = MUtility.PointerToString(bi.pszDisplayName)
  510.     BrowseForFolder = PathFromPidl(pidlOut)
  511.     
  512.     ' Free the pidls we create
  513.     If IsMissing(Root) Then
  514.         Allocator.Free pidlIn
  515.     ElseIf VarType(Root) = vbString Then
  516.         Allocator.Free pidlIn
  517.     Else
  518.         ' Leave the pidl we received as a parameter
  519.     End If
  520.  
  521. End Function
  522.  
  523. #If fComponent = 0 Then
  524. Private Sub ErrRaise(e As Long)
  525.     Dim sText As String, sSource As String
  526.     If e > 1000 Then
  527.         sSource = App.ExeName & ".FoldTool"
  528.         Select Case e
  529.         Case eeBaseFoldTool
  530.             BugAssert True
  531.        ' Case ee...
  532.        '     Add additional errors
  533.         End Select
  534.         Err.Raise COMError(e), sSource, sText
  535.     Else
  536.         ' Raise standard Visual Basic error
  537.         sSource = App.ExeName & ".VBError"
  538.         Err.Raise e, sSource
  539.     End If
  540. End Sub
  541. #End If
  542.  
  543.